import 'package:flutter/material.dart';
import 'package:hi_cache/hi_cache.dart';
import 'package:net/http/dao/account_dao.dart';
import 'package:net/model/video_model.dart';
import 'package:net/navigator/hi_navigator.dart';
import 'package:net/page/dark_mode_page.dart';
import 'package:net/page/home.dart';
import 'package:net/page/login_page.dart';
import 'package:net/page/notice_page.dart';
import 'package:net/page/registration_page.dart';
import 'package:net/page/video_detail.dart';
import 'package:net/provider/hi_provider.dart';
import 'package:net/provider/theme_provider.dart';
import 'package:hi_base/toast.dart';
import 'package:provider/provider.dart';
import 'package:net/utils/hi_defend.dart';

import 'navigator/bottom_navigator.dart';
import 'dart:async';

// Wrap your 'runApp(MyApp())' as follows:

void main() async {
  // WidgetsFlutterBinding.ensureInitialized();
  HiDefend().run(HApp());
}

class HApp extends StatefulWidget {
  HApp({Key key}) : super(key: key);

  @override
  _HAppState createState() => _HAppState();
}

class _HAppState extends State<HApp> {
  // 实例化
  HRouteDelegate _routeDelegate = HRouteDelegate();
  // HRouteInformationParser _routeInformationParser = HRouteInformationParser();
  var listener;
  @override
  void initState() {
    super.initState();
    HiNavigator.geInstance().addListener(this.listener = (current, pre) {
      // 获取到之前的页面，当前的页面 以及当前页面是否压栈
      // print('home:current:${current.page}');
      // print('home:pre:${pre?.page}');
      if (widget == current.page || current.page is HomePage) {
        // print('打开了首页:onResume');
      } else if (widget == pre?.page || pre?.page is HomePage) {
        // print('首页:onPause');
      }
    });
  }

  @override
  void dispose() {
    HiNavigator.geInstance().removeListener(this.listener);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<HiCache>(
      // 进行初始化
      future: HiCache.preInit(),
      builder: (context, AsyncSnapshot<HiCache> snapshot) {
        // 定义route
        var route = snapshot.connectionState == ConnectionState.done
            ? Router(
                routerDelegate: _routeDelegate,
                // 下面这个参数可缺省（没有） 与第一个参数配套使用，第一个有个下面这个就必须有
                // 他们都是针对web端存在的 移动端可以没有 相关的都可以注释掉
                // routeInformationParser: _routeInformationParser,
                // routeInformationProvider: PlatformRouteInformationProvider(
                //   initialRouteInformation: RouteInformation(location: '/'),
              )
            : Scaffold(
                body: Center(child: CircularProgressIndicator()),
              );

        return MultiProvider(
          providers: topProviders,
          // 还可以通过 context.read<xx>
          child: Consumer<ThemeProvider>(
            builder: (context, themeProvider, child) {
              return MaterialApp(
                title: 'Bili', //快照名
                home: route,
                // theme: ThemeData(primarySwatch: white),
                theme: themeProvider.getTheme(),
                darkTheme: themeProvider.getTheme(isDarkMode: true),
                themeMode: themeProvider.getThemeMode(),
                debugShowCheckedModeBanner: false,
              );
            },
          ),
        );
      },
    );
  }
}

// 创建路由的代理 --继承ChangeNotifier和PopNavigat..为了更好的实现 ---步骤二
class HRouteDelegate extends RouterDelegate<HRoutePath>
    with ChangeNotifier, PopNavigatorRouterDelegateMixin<HRoutePath> {
  final GlobalKey<NavigatorState>
      navigatorKey; //为navigatorKey设置一个key，必要的时候可以通过navigatorKey.currentState来获取到NavigatorState对象
  //也是为了实现 里面必须要获取 navigatorKey 这个名字是固定的不能乱写

  HRouteDelegate() : navigatorKey = GlobalKey<NavigatorState>() {
    // 每次调用HRouteDelegate初始化navigatorKey的同时，初始化自己封装的HiNavigator并实例化里面相关参数
    // 最终所有的跳转和传参都会走到这里来
    HiNavigator.geInstance().registerRouteJump(RouteJumpListener(
      onJumpTo: (RouteStatus routeStatus, {Map args}) {
        _routeStatus = routeStatus;
        // 这里处理传参  要是这样搞就很麻烦了 所以这玩意videoModel类型不能写死
        if (routeStatus == RouteStatus.detail) {
          this.videoModel = args['videoMo'];
        }
        // if (args != null) {
        //这是我自己创造的--如果用这种，那每一个可以导航的页面构造函数里面都需要有个可选参数为args 这样下面的page那套if也就可以一起处理了，
        // 也可以到时候搞个list弄个白名单，在里面的就不传args不在里面的都需要传
        //   this.args = args;
        // }
        notifyListeners();
      },
    ));
  } //navigationKey初始化
  List<MaterialPage> pages = []; //存放所有页面的集合
  // HRoutePath path;
  VideoModel videoModel; //这玩意是根据业务来的 --我艹
  // var args;

  RouteStatus _routeStatus = RouteStatus.home; //初始值

  @override
  Widget build(BuildContext context) {
    // 获取当前路由在路由栈中的位置
    int index = getPageIndex(pages, routeStatus);
    List<MaterialPage> tempPages = pages;
    if (index != -1) {
      //要打开的页面在栈中已存在，则将该页面和它上面的所有页面进行出栈
      //tips 具体规则可以根据需要进行调整，这里要求栈中只允许有一个同样的页面的实例
      tempPages = tempPages.sublist(0, index);
    }
    // 构建路由堆栈
    var page;
    // 如果用这种方式 不仅页面冗余 而且每次跳转路径是，传递的参数还要处理--后面需要优化
    if (routeStatus == RouteStatus.home) {
      //跳转首页时将栈中其它页面进行出栈，因为首页不可回退
      pages.clear();
      // page = pageWrap(HomePage());
      page = pageWrap(BottomNavigator());
    } else if (routeStatus == RouteStatus.detail) {
      page = pageWrap(VideoDetailPage(videoModel));
    } else if (routeStatus == RouteStatus.registration) {
      page = pageWrap(HRegis());
    } else if (routeStatus == RouteStatus.login) {
      page = pageWrap(HLogin());
    } else if (routeStatus == RouteStatus.notice) {
      page = pageWrap(NoticePage());
    } else if (routeStatus == RouteStatus.darkModePage) {
      page = pageWrap(DarkModePage());
    }
    //重新创建一个数组，否则pages因引用没有改变路由不会生效--让page在最后一个
    tempPages = [...tempPages, page];
    // 通知路由发生变化
    HiNavigator.geInstance().notify(tempPages, pages);
    pages = tempPages;
    // pages = [
    //   pageWrap(HomePage(
    //     onJumpToDetail: (videoModel) {
    //       this.videoModel = videoModel;
    //       notifyListeners();
    //     },
    //   )),
    //   if (videoModel != null) pageWrap(VideoDetailPage(videoModel))
    // ];
    // 用willPopScope
    return WillPopScope(
      onWillPop: () async =>
          !await navigatorKey.currentState.maybePop(), //修复安卓物理返回键问题
      child: Navigator(
          key: navigatorKey,
          pages: pages,
          onPopPage: (route, result) {
            // 测试返回拦击 -----登录页未登录返回拦截
            if (route.settings is MaterialPage) {
              if ((route.settings as MaterialPage).child is HLogin) {
                if (!hasLogin) {
                  showWarnToast("请先登录");
                  return false;
                }
              }
            }
            // 在这里可以控制是否可以返回
            if (!route.didPop(result)) {
              return false;
            }
            var tempPages = [...pages];
            pages.removeLast(); //返回拦截后 出栈
            //  通知路由发生辩护
            HiNavigator.geInstance().notify(pages, tempPages);
            return true;
          }),
    );
  }

  @override
  Future<void> setNewRoutePath(HRoutePath configuration) async {
    // this.path = configuration;
  }

  // 获取路由状态--为了上面拿到路由位置
  RouteStatus get routeStatus {
    // 路由拦截 ---------

    // 判断如果不是注册页面也还没登录 ，就让它去登录页面
    if (_routeStatus != RouteStatus.registration && !hasLogin) {
      return _routeStatus = RouteStatus.login;
    } else {
      return _routeStatus;
    }
  }

  // 判断是否能够拿到令牌--确定是否登录
  bool get hasLogin => LoginDao.getBoardingPass() != null;
}

//RouteInformationParser可解析为我们定义的数据类型 ---步骤一
// class HRouteInformationParser extends RouteInformationParser<HRoutePath> {
//   @override
//   Future<HRoutePath> parseRouteInformation(
//       RouteInformation routeInformation) async {
//     final uri = Uri.parse(routeInformation.location);
//     if (uri.pathSegments.length == 0) {
//       //说明是首页
//       return HRoutePath.home();
//     }
//     return HRoutePath.detail();
//   }
// }

// 定义路由数据 ，path
class HRoutePath {
  final String location;
  HRoutePath.home() : location = '/';
  HRoutePath.detail() : location = '/detail';
}

/*
context.watch<T>()， 一方法使得widget能够监听泛型T上发生的改变。
context.read<T>()，直接返回T，不会监听改变。
context.select<T， R>(R cb(T value))，允许widget只监听T上的一部分(R)。
需要注意的是，context.read<T>() 方法不会在值变化时重新widget， 并且不能在 StatelessWidget.build/State.build 方法内调用，如果要要在build中访问provider中的数据可以用 context.watch<T>()代替。

context.xxx是provider 4.1.0中给BuildContext添加的拓展方法，等价于Provider.of<T>(context)。

Provider.of<T>(context)类似 context.watch<T>()；
Provider.of<T>(context，listen: false)类似于 context.read<T>()；
推荐consumer
*/
